﻿#include "Reader.h"
#include "Types.h"
#include "Util.h"
#include "Exception.h"

#include <iostream>
#include <regex>

namespace Lisp
{
  static const std::regex s_tokenRgx(R"([ \t,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|\r?\n|[^\s\[\]{}('"`,;)]*))");
  static const std::regex s_closeRegex("[\\)\\]}]");
  static const std::regex s_intRgx(R"(^[-+]?\d+$)");
  static const std::regex s_strRgx(R"(^\"([^"]*)\"$)");

  Reader::Reader(const std::vector<std::string> &tokens)
      : m_index(0), m_tokens(std::move(tokens)) {}

  Reader::~Reader() {}

  size_t Reader::Next()
  {
    if (m_index + 1 >= m_tokens.size())
    {
      return -1;
    }
    return ++m_index;
  }

  const std::string &Reader::Peek() const
  {
    return m_tokens[m_index];
  }

  ValPtr ReadStr(const std::string &input)
  {
    Reader reader(std::move(Tokenize(input)));
    if (!reader.HasNext())
    {
      throw EmptyInputException();
    }
    return ReadFrom(reader);
  }

  ValPtr ReadFrom(Reader &reader)
  {
    LISP_CHECK(reader.HasNext(), "Got eof");

    const std::string &token = reader.Peek();

    LISP_CHECK(!std::regex_match(token, s_closeRegex), "Unmatched close parenthesis");

    if (token == "(")
    {
      reader.Next();
      Scoped<ValVec> list(new ValVec);
      ReadList(reader, list.get(), ")");
      return ListVal(list.release());
    }
    else if (token == "[")
    {
      reader.Next();
      Scoped<ValVec> list(new ValVec);
      ReadList(reader, list.get(), "]");
      return VectorVal(list.release());
    }
    else if (token == "{")
    {
      reader.Next();
      ValVec list;
      ReadList(reader, &list, "}");
      return HashVal(list.begin(), list.end(), false);
    }
    return ReadAtom(reader);
  }

  ValPtr ReadAtom(Reader &reader)
  {
    struct ReaderMacro
    {
      std::string name;
      std::string symbol;
    };

    struct Constant
    {
      std::string name;
      ValPtr value;
    };

    static const ReaderMacro macroTable[] = {
        {"@", "deref"},
        {"'", "quote"},
        {"`", "quasiquote"},
        {"~", "unquote"},
        {"~@", "splice-unquote"},
    };

    static const Constant constantTable[] = {
        {"nil", NilVal()},
        {"true", TrueVal()},
        {"false", FalseVal()},
    };

    const std::string &token = reader.Peek();
    if (token.empty())
    {
      throw RuntimeException("Empty token");
    }

    if (std::regex_match(token, s_intRgx))
    {
      return IntegerVal(token);
    }
    else if (token[0] == '"')
    {
      if (token.size() > 1 && token.back() == '"')
      {
        return StringVal(StringUtils::Unescape(token));
      }
      else
      {
        throw RuntimeException("Unterminated string");
      }
    }
    else if (token[0] == ':')
    {
      return KeywordVal(token);
    }

    for (const auto &constant : constantTable)
    {
      if (token == constant.name)
      {
        return constant.value;
      }
    }

    for (const auto &macro : macroTable)
    {
      if (token == macro.name)
      {
        reader.Next();
        return ListVal({SymbolVal(macro.symbol), ReadFrom(reader)});
      }
    }

    return SymbolVal(token);
  }

  void ReadList(Reader &reader, ValVec *list, const std::string &end)
  {
    std::string token = reader.Peek();
    while (token != end)
    {
      list->push_back(ReadFrom(reader));
      reader.Next();
      token = reader.Peek();
    }
  }

  std::vector<std::string> Tokenize(const std::string &str)
  {

    std::sregex_iterator begin(str.begin(), str.end(), s_tokenRgx);
    std::sregex_iterator end;
    std::vector<std::string> tokens;

    int lineNo = 1;

    for (auto it = begin; it != end; ++it)
    {
      std::string token = it->str(1);

      if (token.size() > 0)
      {
        if (token[0] == ';')
          continue;
        if (token == "\n" || token == "\r\n")
        {
          lineNo++;
          continue;
        }
        if (token[0] == '#')
        {
          if (token == "#line")
          {
            token = std::to_string(lineNo);
          }
        }

        tokens.push_back(token);
      }
    }
    return std::move(tokens);
  }

}